using System;
using System.Collections.Generic;
using System.Net;
using CodePlex.TfsLibrary.ObjectModel;
using CodePlex.TfsLibrary.RepositoryWebSvc;
using Xunit;

namespace CodePlex.TfsLibrary.ClientEngine
{
    public class TfsEngineSetupWorkspaceTest
    {
        public class SetupWorkspace
        {
            [Fact]
            public void LocalMappingAdded()
            {
                string workspaceName;
                TestableTfsEngine engine = TestableTfsEngine.Create();
                engine.FileSystem.EnsurePath(@"C:\Foo");
                engine.TfsState.TrackFolder("http://tfsUrl", "$/", @"C:\Foo", 0, 0, SourceItemStatus.Unmodified);

                using (TfsWorkspace workspace = engine.SetupWorkspace(@"C:\Foo", true))
                    workspaceName = workspace.Name;

                Assert.Equal("http://tfsUrl", engine.SourceControlService.AddWorkspaceMapping_TfsUrl);
                Assert.Equal("$/", engine.SourceControlService.AddWorkspaceMapping_ServerPath);
                Assert.Equal(@"C:\Foo", engine.SourceControlService.AddWorkspaceMapping_LocalPath);
                Assert.Equal(workspaceName, engine.SourceControlService.AddWorkspaceMapping_WorkspaceName);
            }

            [Fact]
            public void ModificationsInHierarchy()
            {
                TestableTfsEngine engine = TestableTfsEngine.Create();
                engine.FileSystem.EnsurePath(@"C:\Foo");
                engine.FileSystem.EnsurePath(@"C:\Foo\Bar");
                engine.FileSystem.EnsurePath(@"C:\Foo\Bar\Baz");
                engine.FileSystem.WriteAllText(@"C:\Foo\file1.txt", "file");
                engine.FileSystem.WriteAllText(@"C:\Foo\Bar\file2.txt", "file");
                engine.FileSystem.WriteAllText(@"C:\Foo\Bar\Baz\file3.txt", "file");
                engine.TfsState.TrackFolder("http://tfsUrl", "$/", @"C:\Foo", 0, 0, SourceItemStatus.Unmodified);
                engine.TfsState.TrackFolder("http://tfsUrl", "$/", @"C:\Foo\Bar", 0, 0, SourceItemStatus.Unmodified);
                engine.TfsState.TrackFolder("http://tfsUrl", "$/", @"C:\Foo\Bar\Baz", 0, 0, SourceItemStatus.Unmodified);
                engine.TfsState.TrackFolder("http://tfsUrl", "$/", @"C:\Foo\Deleted", 0, 0, SourceItemStatus.Delete);
                engine.TfsState.TrackFile(@"C:\Foo\file1.txt", 1, 2, SourceItemStatus.Unmodified);
                engine.TfsState.TrackFile(@"C:\Foo\Bar\file2.txt", 3, 4, SourceItemStatus.Add);
                engine.TfsState.TrackFile(@"C:\Foo\Bar\Baz\file3.txt", 5, 6, SourceItemStatus.Unmodified);
                engine.FileSystem.WriteAllText(@"C:\Foo\Bar\Baz\file3.txt", "this file has been modified");

                using (engine.SetupWorkspace(@"C:\Foo", true)) {}

                List<string> adds = new List<string>();
                List<string> edits = new List<string>();
                List<string> deletes = new List<string>();

                foreach (PendRequest request in engine.SourceControlService.PendChanges_Requests)
                    if (request.RequestType == PendRequestType.Add)
                        adds.Add(request.LocalName);
                    else if (request.RequestType == PendRequestType.Edit)
                        edits.Add(request.LocalName);
                    else if (request.RequestType == PendRequestType.Delete)
                        deletes.Add(request.LocalName);

                Assert.True(adds.Contains(@"C:\Foo\Bar\file2.txt"));
                Assert.True(edits.Contains(@"C:\Foo\Bar\Baz\file3.txt"));
                Assert.True(deletes.Contains(@"C:\Foo\Deleted"));
            }

            [Fact]
            public void NoModifications()
            {
                string workspaceName;
                TestableTfsEngine engine = TestableTfsEngine.Create();
                engine.FileSystem.EnsurePath(@"C:\Foo");
                engine.FileSystem.WriteAllText(@"C:\Foo\bar.txt", "file");
                engine.TfsState.TrackFolder("http://tfsUrl", "$/", @"C:\Foo", 1, 2, SourceItemStatus.Unmodified);
                engine.TfsState.TrackFile(@"C:\Foo\bar.txt", 3, 4, SourceItemStatus.Unmodified);

                using (TfsWorkspace workspace = engine.SetupWorkspace(@"C:\Foo", true))
                    workspaceName = workspace.Name;

                Assert.Equal("http://tfsUrl", engine.SourceControlService.UpdateLocalVersions_TfsUrl);
                Assert.Equal(workspaceName, engine.SourceControlService.UpdateLocalVersions_WorkspaceName);
                Assert.Equal(2, engine.SourceControlService.UpdateLocalVersions_Updates.Count);
                Assert.Equal(1, engine.SourceControlService.UpdateLocalVersions_Updates[0].ItemId);
                Assert.Equal(2, engine.SourceControlService.UpdateLocalVersions_Updates[0].LocalChangesetID);
                Assert.Equal(@"C:\Foo", engine.SourceControlService.UpdateLocalVersions_Updates[0].LocalName);
                Assert.Equal(3, engine.SourceControlService.UpdateLocalVersions_Updates[1].ItemId);
                Assert.Equal(4, engine.SourceControlService.UpdateLocalVersions_Updates[1].LocalChangesetID);
                Assert.Equal(@"C:\Foo\bar.txt", engine.SourceControlService.UpdateLocalVersions_Updates[1].LocalName);
            }

            [Fact]
            public void NoModificationsInHierarchy()
            {
                TestableTfsEngine engine = TestableTfsEngine.Create();
                engine.FileSystem.EnsurePath(@"C:\Foo");
                engine.FileSystem.EnsurePath(@"C:\Foo\Bar");
                engine.FileSystem.EnsurePath(@"C:\Foo\Bar\Baz");
                engine.FileSystem.WriteAllText(@"C:\Foo\file1.txt", "file");
                engine.FileSystem.WriteAllText(@"C:\Foo\Bar\file2.txt", "file");
                engine.FileSystem.WriteAllText(@"C:\Foo\Bar\Baz\file3.txt", "file");
                engine.TfsState.TrackFolder("http://tfsUrl", "$/", @"C:\Foo", 10, 11, SourceItemStatus.Unmodified);
                engine.TfsState.TrackFolder("http://tfsUrl", "$/", @"C:\Foo\Bar", 12, 13, SourceItemStatus.Unmodified);
                engine.TfsState.TrackFolder("http://tfsUrl", "$/", @"C:\Foo\Bar\Baz", 14, 15, SourceItemStatus.Unmodified);
                engine.TfsState.TrackFile(@"C:\Foo\file1.txt", 1, 2, SourceItemStatus.Unmodified);
                engine.TfsState.TrackFile(@"C:\Foo\Bar\file2.txt", 3, 4, SourceItemStatus.Unmodified);
                engine.TfsState.TrackFile(@"C:\Foo\Bar\Baz\file3.txt", 5, 6, SourceItemStatus.Unmodified);

                using (engine.SetupWorkspace(@"C:\Foo", true)) {}

                List<string> updates = new List<string>();

                foreach (LocalUpdate update in engine.SourceControlService.UpdateLocalVersions_Updates)
                    updates.Add(update.LocalName);

                Assert.True(updates.Contains(@"C:\Foo\file1.txt"));
                Assert.True(updates.Contains(@"C:\Foo\Bar"));
                Assert.True(updates.Contains(@"C:\Foo\Bar\file2.txt"));
                Assert.True(updates.Contains(@"C:\Foo\Bar\Baz"));
                Assert.True(updates.Contains(@"C:\Foo\Bar\Baz\file3.txt"));
            }

            [Fact]
            public void NoStateChange()
            {
                TestableTfsEngine engine = TestableTfsEngine.Create();
                engine.FileSystem.EnsurePath(@"C:\Foo");
                engine.TfsState.TrackFolder("http://tfsUrl", "$/", @"C:\Foo", 0, 0, SourceItemStatus.Unmodified);

                using (engine.SetupWorkspace(@"C:\Foo", true)) {}

                Assert.Null(engine.SourceControlService.PendChanges_TfsUrl);
                Assert.Null(engine.SourceControlService.PendChanges_Credentials);
                Assert.Null(engine.SourceControlService.PendChanges_WorkspaceName);
                Assert.Null(engine.SourceControlService.PendChanges_Requests);
            }

            [Fact]
            public void WorkspaceCreated()
            {
                string workspaceName;
                TestableTfsEngine engine = TestableTfsEngine.Create();
                engine.FileSystem.EnsurePath(@"C:\Foo");
                engine.TfsState.TrackFolder("http://tfsUrl", "$/", @"C:\Foo", 0, 0, SourceItemStatus.Unmodified);

                using (TfsWorkspace workspace = engine.SetupWorkspace(@"C:\Foo", true))
                    workspaceName = workspace.Name;

                Assert.Equal("http://tfsUrl", engine.SourceControlService.CreateWorkspace_TfsUrl);
                Assert.Equal(workspaceName, engine.SourceControlService.CreateWorkspace_WorkspaceName);
            }
        }

        public class SetupWorkspace_Credentials
        {
            [Fact]
            public void AddWorkspaceMapping_CleanupCalled()
            {
                NetworkCredential creds = new NetworkCredential("joe", "bob");
                string originalWorkspaceName = null;
                TestableTfsEngine engine = CreateEngine();
                engine.SourceControlService.AddWorkspaceMapping__ThrowsCredExceptionOnce = true;
                engine.CredentialsCallback = delegate
                                             {
                                                 originalWorkspaceName = engine.SourceControlService.CreateWorkspace_WorkspaceName;
                                                 return creds;
                                             };

                engine.SetupWorkspace(@"C:\Foo", true);

                Assert.NotNull(originalWorkspaceName);
                Assert.Equal(originalWorkspaceName, engine.SourceControlService.DeleteWorkspace_WorkspaceName);
            }

            [Fact]
            public void AddWorkspaceMappingFailure()
            {
                string callbackTfsUrl = null;
                TestableTfsEngine engine = CreateEngine();
                engine.SourceControlService.AddWorkspaceMapping__ThrowsCredExceptionOnce = true;
                engine.CredentialsCallback = delegate(ICredentials oldCredentials,
                                                      string tfsUrl)
                                             {
                                                 callbackTfsUrl = tfsUrl;
                                                 return null;
                                             };

                try
                {
                    engine.SetupWorkspace(@"C:\Foo", true);
                    throw new InvalidOperationException("Expected a throw!");
                }
                catch (NetworkAccessDeniedException) {}

                Assert.Equal("http://tfsUrl", callbackTfsUrl);
            }

            [Fact]
            public void CallsBackForCredentialsOnStartup()
            {
                NetworkCredential creds = new NetworkCredential("joe", "bob");
                TestableTfsEngine engine = CreateEngine();
                engine.CredentialsCallback = delegate
                                             {
                                                 Assert.False(engine.SourceControlService.CreateWorkspace__Called);
                                                 return creds;
                                             };

                engine.SetupWorkspace(@"C:\Foo", true);

                Assert.True(engine.SourceControlService.CreateWorkspace__Called);
                Assert.Same(creds, engine.SourceControlService.CreateWorkspace_Credentials);
            }

            static TestableTfsEngine CreateEngine()
            {
                TestableTfsEngine engine = TestableTfsEngine.Create();
                engine.FileSystem.EnsurePath(@"C:\Foo");
                engine.FileSystem.WriteAllText(@"C:\Foo\bar.txt", "text");
                engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo", @"C:\Foo", 1, 1, SourceItemStatus.Unmodified);
                engine.TfsState.TrackFile(@"C:\Foo\bar.txt", 0, 0, SourceItemStatus.Add);
                return engine;
            }

            [Fact]
            public void CreateWorkspaceFailure()
            {
                string callbackTfsUrl = null;
                TestableTfsEngine engine = CreateEngine();
                engine.SourceControlService.CreateWorkspace__ThrowsCredExceptionOnce = true;
                engine.CredentialsCallback = delegate(ICredentials oldCredentials,
                                                      string tfsUrl)
                                             {
                                                 callbackTfsUrl = tfsUrl;
                                                 return null;
                                             };

                try
                {
                    engine.SetupWorkspace(@"C:\Foo", true);
                    throw new InvalidOperationException("Expected a throw!");
                }
                catch (NetworkAccessDeniedException) {}

                Assert.Equal("http://tfsUrl", callbackTfsUrl);
            }

            [Fact]
            public void CreateWorkspaceFailure_NoCleanup()
            {
                NetworkCredential creds = new NetworkCredential("joe", "bob");
                TestableTfsEngine engine = CreateEngine();
                engine.SourceControlService.CreateWorkspace__ThrowsCredExceptionOnce = true;
                engine.CredentialsCallback = delegate
                                             {
                                                 return creds;
                                             };

                engine.SetupWorkspace(@"C:\Foo", true);

                Assert.Null(engine.SourceControlService.DeleteWorkspace_WorkspaceName);
            }

            [Fact]
            public void FailWithNoCredentialCallback()
            {
                TestableTfsEngine engine = CreateEngine();
                engine.SourceControlService.CreateWorkspace__ThrowsCredExceptionOnce = true;

                Assert.Throws<NetworkAccessDeniedException>(delegate
                                                            {
                                                                engine.SetupWorkspace(@"C:\Foo", true);
                                                            });
            }

            [Fact]
            public void Retried()
            {
                NetworkCredential creds = new NetworkCredential("jim", "jeff");
                string callbackTfsUrl = null;
                TestableTfsEngine engine = CreateEngine();
                engine.SourceControlService.CreateWorkspace__ThrowsCredExceptionOnce = true;
                engine.CredentialsCallback = delegate(ICredentials oldCredentials,
                                                      string tfsUrl)
                                             {
                                                 callbackTfsUrl = tfsUrl;
                                                 return creds;
                                             };

                engine.SetupWorkspace(@"C:\Foo", true);

                Assert.Equal("http://tfsUrl", callbackTfsUrl);
                Assert.Same(creds, engine.SourceControlService.AddWorkspaceMapping_Credentials);
            }
        }

        public class SetupWorkspace_File
        {
            [Fact]
            public void AddedFile()
            {
                TestableTfsEngine engine = TestableTfsEngine.Create();
                engine.FileSystem.EnsurePath(@"C:\Foo");
                engine.FileSystem.WriteAllText(@"C:\Foo\add.txt", "file");
                engine.TfsState.TrackFolder("http://tfsUrl", "$/", @"C:\Foo", 0, 0, SourceItemStatus.Unmodified);
                engine.TfsState.TrackFile(@"C:\Foo\add.txt", 1, 2, SourceItemStatus.Add);

                using (engine.SetupWorkspace(@"C:\Foo", true)) {}

                Assert.Equal(1, engine.SourceControlService.PendChanges_Requests.Count);
                Assert.Equal(@"C:\Foo\add.txt", engine.SourceControlService.PendChanges_Requests[0].LocalName);
                Assert.Equal(PendRequestType.Add, engine.SourceControlService.PendChanges_Requests[0].RequestType);
                Assert.Equal(ItemType.File, engine.SourceControlService.PendChanges_Requests[0].ItemType);
            }

            [Fact]
            public void DeletedFile()
            {
                TestableTfsEngine engine = TestableTfsEngine.Create();
                engine.FileSystem.EnsurePath(@"C:\Foo");
                engine.TfsState.TrackFolder("http://tfsUrl", "$/", @"C:\Foo", 0, 0, SourceItemStatus.Unmodified);
                engine.TfsState.TrackFile(@"C:\Foo\delete.txt", 1, 2, SourceItemStatus.Delete);

                using (engine.SetupWorkspace(@"C:\Foo", true)) {}

                Assert.Equal(1, engine.SourceControlService.PendChanges_Requests.Count);
                Assert.Equal(@"C:\Foo\delete.txt", engine.SourceControlService.PendChanges_Requests[0].LocalName);
                Assert.Equal(PendRequestType.Delete, engine.SourceControlService.PendChanges_Requests[0].RequestType);
            }

            [Fact]
            public void ModifiedFile()
            {
                string workspaceName;
                TestableTfsEngine engine = TestableTfsEngine.Create();
                engine.FileSystem.EnsurePath(@"C:\Foo");
                engine.FileSystem.WriteAllText(@"C:\Foo\modified.txt", "file");
                engine.TfsState.TrackFolder("http://tfsUrl", "$/", @"C:\Foo", 0, 0, SourceItemStatus.Unmodified);
                engine.TfsState.TrackFile(@"C:\Foo\modified.txt", 1, 2, SourceItemStatus.Unmodified);
                engine.FileSystem.WriteAllText(@"C:\Foo\modified.txt", "modified file");

                using (TfsWorkspace workspace = engine.SetupWorkspace(@"C:\Foo", true))
                    workspaceName = workspace.Name;

                Assert.Equal("http://tfsUrl", engine.SourceControlService.PendChanges_TfsUrl);
                Assert.Equal(workspaceName, engine.SourceControlService.PendChanges_WorkspaceName);
                Assert.Equal(1, engine.SourceControlService.PendChanges_Requests.Count);
                Assert.Equal(@"C:\Foo\modified.txt", engine.SourceControlService.PendChanges_Requests[0].LocalName);
                Assert.Equal(PendRequestType.Edit, engine.SourceControlService.PendChanges_Requests[0].RequestType);
            }

            [Fact]
            public void ModifiedFileWithoutPendingEdits()
            {
                TestableTfsEngine engine = TestableTfsEngine.Create();
                engine.FileSystem.EnsurePath(@"C:\Foo");
                engine.FileSystem.WriteAllText(@"C:\Foo\modified.txt", "file");
                engine.TfsState.TrackFolder("http://tfsUrl", "$/", @"C:\Foo", 0, 0, SourceItemStatus.Unmodified);
                engine.TfsState.TrackFile(@"C:\Foo\modified.txt", 1, 2, SourceItemStatus.Unmodified);
                engine.FileSystem.WriteAllText(@"C:\Foo\modified.txt", "modified file");

                using (engine.SetupWorkspace(@"C:\Foo", false)) {}

                Assert.Null(engine.SourceControlService.PendChanges_Requests);
            }

            [Fact]
            public void UnversionedFile()
            {
                TestableTfsEngine engine = TestableTfsEngine.Create();
                engine.FileSystem.EnsurePath(@"C:\Foo");
                engine.FileSystem.WriteAllText(@"C:\Foo\unversioned.txt", "file");
                engine.TfsState.TrackFolder("http://tfsUrl", "$/", @"C:\Foo", 0, 0, SourceItemStatus.Unmodified);

                using (engine.SetupWorkspace(@"C:\Foo", true)) {}

                Assert.Null(engine.SourceControlService.PendChanges_Requests);
            }
        }

        public class SetupWorkspace_Folder
        {
            [Fact]
            public void AddedFolder()
            {
                TestableTfsEngine engine = TestableTfsEngine.Create();
                engine.FileSystem.EnsurePath(@"C:\Foo");
                engine.FileSystem.EnsurePath(@"C:\Foo\Bar");
                engine.TfsState.TrackFolder("http://tfsUrl", "$/", @"C:\Foo", 0, 0, SourceItemStatus.Unmodified);
                engine.TfsState.TrackFolder("http://tfsUrl", "$/", @"C:\Foo\Bar", 0, 0, SourceItemStatus.Add);

                using (engine.SetupWorkspace(@"C:\Foo", true)) {}

                Assert.Equal(1, engine.SourceControlService.PendChanges_Requests.Count);
                Assert.Equal(@"C:\Foo\Bar", engine.SourceControlService.PendChanges_Requests[0].LocalName);
                Assert.Equal(PendRequestType.Add, engine.SourceControlService.PendChanges_Requests[0].RequestType);
                Assert.Equal(ItemType.Folder, engine.SourceControlService.PendChanges_Requests[0].ItemType);
            }

            [Fact]
            public void DeletedFolder()
            {
                TestableTfsEngine engine = TestableTfsEngine.Create();
                engine.FileSystem.EnsurePath(@"C:\Foo");
                engine.TfsState.TrackFolder("http://tfsUrl", "$/", @"C:\Foo", 0, 0, SourceItemStatus.Unmodified);
                engine.TfsState.TrackFolder("http://tfsUrl", "$/", @"C:\Foo\Bar", 0, 0, SourceItemStatus.Delete);

                using (engine.SetupWorkspace(@"C:\Foo", true)) {}

                Assert.Equal(1, engine.SourceControlService.PendChanges_Requests.Count);
                Assert.Equal(@"C:\Foo\Bar", engine.SourceControlService.PendChanges_Requests[0].LocalName);
                Assert.Equal(PendRequestType.Delete, engine.SourceControlService.PendChanges_Requests[0].RequestType);
            }

            [Fact]
            public void UnversionedFolder()
            {
                TestableTfsEngine engine = TestableTfsEngine.Create();
                engine.FileSystem.EnsurePath(@"C:\Foo");
                engine.FileSystem.EnsurePath(@"C:\Foo\Bar");
                engine.TfsState.TrackFolder("http://tfsUrl", "$/", @"C:\Foo", 0, 0, SourceItemStatus.Unmodified);

                using (engine.SetupWorkspace(@"C:\Foo", true)) {}

                Assert.Null(engine.SourceControlService.PendChanges_Requests);
            }
        }
    }
}